/*
 * Copyright (c) 2009 Frederic Daoud
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.stripesbook.rayures.view;

import static java.util.Collections.list;
import static org.stripesbook.rayures.util.Constants.ATTR_BUNDLE;
import static org.stripesbook.rayures.util.Constants.ATTR_VIEW;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.stripesbook.rayures.service.ResourceBundleResolver;
import org.stripesbook.rayures.service.TemplateEngine;
import org.stripesbook.rayures.util.MapBundle;

/**
 * A view template is a Java class that has an associated template (e.g. a Velocity template). The
 * template gets rendered and uses the Java class as a helper to encapsulate any logic that is
 * easier to implement in Java code rather than template code.
 *
 * @author Frederic Daoud
 *
 * TODO: cache templates in production mode.
 */
public class ViewTemplate<V extends ViewTemplate<?>> extends ViewObject<V> {
  private Map<String, Object> dataModel = new HashMap<String, Object>();
  private TemplateEngine templateEngine;
  private ResourceBundleResolver bundleResolver;
  private Locale locale;
  private MapBundle templateBundle = new MapBundle();
  private String templateFile;
  private Class<?> templateClass;

  @Override
  public void setViewContext(ViewContext viewContext) {
    templateEngine = viewContext.getTemplateEngine();
    bundleResolver = viewContext.getResourceBundleResolver();

    HttpServletRequest request = viewContext.getRequest();
    locale = request.getLocale();

    templateBundle.putAll(viewContext.getBundle());
    templateBundle.putAll(bundleResolver.getBundleMap(getClass().getName(), locale));

    setTemplateBundle(viewContext);
  }

  private void setTemplateBundle(ViewContext vc) {
    put(ATTR_BUNDLE, templateBundle);
    vc.setBundle(templateBundle);
    super.setViewContext(vc);
  }

  public V put(String key, Object object) {
    dataModel.put(key, object);
    return getThis();
  }

  public Object get(String key) {
    return dataModel.get(key);
  }

  /**
   * Optionally set the template file explicitly. It is not necessary to call this method when the
   * default of using a template file that corresponds to the fully qualified class name is
   * satisfactory. Using this method is convenient, however, when a template does not need any
   * associated Java code.
   */
  public V templateFile(String templateFile) {
    this.templateFile = templateFile;
    return getThis();
  }

  /**
   * Optionally set the template class explicitly. If the template file is not set via
   * {@link #templateFile(String)}, the class specified here will be used to find the corresponding
   * template file.
   */
  public V templateClass(Class<?> templateClass) {
    this.templateClass = templateClass;

    templateBundle.putAll(bundleResolver.getBundleMap(templateClass.getName(), locale));
    setTemplateBundle(viewContext);

    return getThis();
  }

  @Override
  public String toString() {
    addAttributesFromRequest();
    String fileName = findTemplateFile();
    return produceOutput(fileName);
  }

  @SuppressWarnings("unchecked")
  protected void addAttributesFromRequest() {
    HttpServletRequest request = viewContext.getRequest();
    List<String> names = list(request.getAttributeNames());
    for (String name : names) {
      if (!dataModel.containsKey(name)) {
        dataModel.put(name, request.getAttribute(name));
      }
    }
  }

  protected String findTemplateFile() {
    if (templateFile != null) {
      return templateFile;
    }

    for (Class<?> currentClass = (templateClass != null ? templateClass : getClass());
      ViewObject.class.isAssignableFrom(currentClass);
      currentClass = currentClass.getSuperclass())
    {
      String fileName = convertClassToFileName(currentClass);
      if (templateEngine.isTemplateFileNameValid(fileName)) {
        return fileName;
      }
    }
    throw new IllegalArgumentException("Could not find a template for " + getClass().getName());
  }

  protected String convertClassToFileName(Class<?> targetClass) {
    String className = targetClass.getName();
    return "/" + className.replaceAll("\\.", "/") + "." + templateEngine.getTemplateFileExtension();
  }

  protected String produceOutput(String fileName) {
    try {
      StringWriter stringWriter = new StringWriter();
      dataModel.put(ATTR_VIEW, this);
      templateEngine.write(fileName, dataModel, stringWriter);
      return stringWriter.toString();
    }
    catch (Exception exc) {
      throw new RuntimeException("Could not produce to output: " + fileName, exc);
    }
  }
}
